package com.morpheus.redis.redisson.config;

import java.net.URL;
import java.util.List;
import java.util.Optional;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.ClusterServersConfig;
import org.redisson.config.Config;
import org.redisson.config.ReadMode;
import org.redisson.config.SubscriptionMode;
import org.redisson.spring.data.connection.RedissonConnectionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.ResourceUtils;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.morpheus.redis.redisson.annotation.ConditionalOnRedissonClusterConfig;
import com.morpheus.redis.redisson.config.RedissonProperties.Connection;
import com.morpheus.redis.redisson.config.RedissonProperties.Connection.Master;
import com.morpheus.redis.redisson.config.RedissonProperties.Connection.Slave;
import com.morpheus.redis.redisson.config.RedissonProperties.Retry;
import com.morpheus.redis.redisson.config.RedissonProperties.Subscription;

@Configuration
@EnableConfigurationProperties({ RedissonProperties.class, RedisProperties.class })
@ConditionalOnRedissonClusterConfig
public class RedissonClusterAutoConfiguration {
	private static final Logger LOGGER = LoggerFactory.getLogger(RedissonClusterAutoConfiguration.class);

	public static final int DEFAULT_MASTER_POOLSIZE = 6;
	public static final int DEFAULT_MASTER_MINIMUMIDLESIZE = 1;

	public static final int DEFAULT_SLAVE_POOLSIZE = 6;
	public static final int DEFAULT_SLAVE_MINIMUMIDLESIZE = 1;

	@Value("${spring.application.name}")
	private String appName = null;

	private Config defaultConfig(RedisProperties redisProperties, RedissonProperties redissonProperties) {
		Config config = new Config();

		Connection connection = redissonProperties.getConnection();
		Retry retry = redissonProperties.getRetry();
		Subscription subscription = redissonProperties.getSubscription();

		List<String> nodes = redisProperties.getCluster().getNodes();
		ClusterServersConfig clusterServersConfig = config.useClusterServers()
				.addNodeAddress(nodes.toArray(new String[nodes.size()])).setPassword(redisProperties.getPassword())
				.setConnectTimeout(Long.valueOf(redisProperties.getTimeout().toMillis()).intValue())
				.setClientName(this.appName);
		if (connection != null) {
			clusterServersConfig
					.setMasterConnectionPoolSize(Optional.ofNullable(connection.getMaster())
							.orElse(this.getDefaultMasterConfig()).getPoolSize())
					.setMasterConnectionMinimumIdleSize(Optional.ofNullable(connection.getMaster())
							.orElse(this.getDefaultMasterConfig()).getMinimumIdleSize())
					.setPingConnectionInterval(connection.getPingInterval())
					.setIdleConnectionTimeout(connection.getIdleTimeout())
					.setSlaveConnectionPoolSize(Optional.ofNullable(connection.getSlave())
							.orElse(this.getDefaultSlaveConfig()).getPoolSize())
					.setSlaveConnectionMinimumIdleSize(Optional.ofNullable(connection.getSlave())
							.orElse(this.getDefaultSlaveConfig()).getMinimumIdleSize());
		}
		clusterServersConfig.setKeepAlive(true).setReadMode(ReadMode
				.valueOf(Optional.ofNullable(redissonProperties.getReadMode()).orElse(ReadMode.MASTER_SLAVE.name())));
		if (retry != null) {
			clusterServersConfig.setRetryAttempts(retry.getAttempts())
					.setRetryInterval(retry.getInterval());
		}
		if (subscription != null) {
			clusterServersConfig.setSubscriptionConnectionMinimumIdleSize(subscription.getConnectionMinimumIdleSize())
					.setSubscriptionConnectionPoolSize(subscription.getConnectionPoolSize())
					.setSubscriptionMode(SubscriptionMode.valueOf(Optional.ofNullable(subscription.getMode()).orElse(SubscriptionMode.SLAVE.name())))
					.setSubscriptionsPerConnection(subscription.getSubscriptionsPerConnection());
		}
		return config;
	}

	private Slave getDefaultSlaveConfig() {
		Slave slave = new Slave();
		slave.setPoolSize(DEFAULT_SLAVE_POOLSIZE);
		slave.setMinimumIdleSize(DEFAULT_SLAVE_MINIMUMIDLESIZE);
		return slave;
	}

	private Master getDefaultMasterConfig() {
		Master master = new Master();
		master.setPoolSize(DEFAULT_SLAVE_POOLSIZE);
		master.setMinimumIdleSize(DEFAULT_SLAVE_MINIMUMIDLESIZE);
		return master;
	}

	public Config config(RedisProperties redisProperties, RedissonProperties redissonProperties) {
		Config config = null;
		try {
			String configName = redissonProperties.getConfig();
			URL url = ResourceUtils.getURL(configName);
			config = Config.fromYAML(url);
			if (!config.isClusterConfig()) {
				throw new IllegalStateException("Invalid config yaml.");
			}
		} catch (Throwable th) {
			LOGGER.error("RedissonClusterAutoConfiguration.config() throwable", th);
			config = null;
		}
		if (config == null) {
			config = this.defaultConfig(redisProperties, redissonProperties);
		}
		return config;
	}

	@Bean
	public RedissonClient redissonClient(RedisProperties redisProperties, RedissonProperties redissonProperties) {
		Config config = this.config(redisProperties, redissonProperties);
		return Redisson.create(config);
	}

	@Bean
	public RedisConnectionFactory connectionFactory(RedissonClient redissonClient) {
		RedissonConnectionFactory connectionFactory = new RedissonConnectionFactory(redissonClient);
		try {
			connectionFactory.afterPropertiesSet();
		} catch (Throwable th) {
			LOGGER.error("RedissonClusterAutoConfiguration.connectionFactory() throwable", th);
		}
		return connectionFactory;
	}

	@Bean
	@ConditionalOnMissingBean
	public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory connectionFactory) {
		StringRedisTemplate stringRedisTemplate = new StringRedisTemplate(connectionFactory);
		return stringRedisTemplate;
	}

	@Bean
	@ConditionalOnMissingBean
	public <V> RedisTemplate<String, V> redisTemplate(
			@Qualifier("connectionFactory") RedisConnectionFactory connectionFactory) {
		RedisTemplate<String, V> template = new RedisTemplate<>();
		template.setConnectionFactory(connectionFactory);

		FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);

		ObjectMapper mapper = new ObjectMapper();
		mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
		mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
		serializer.setObjectMapper(mapper);

		template.setValueSerializer(serializer);
		// ʹ��StringRedisSerializer�����л��ͷ����л�redis��keyֵ
		template.setKeySerializer(new StringRedisSerializer());
		template.afterPropertiesSet();
		return template;
	}
}
